gh-72902: speedup Fraction.from_decimal/float in typical cases#133251
Conversation
Here patch2 is the current version. v1 had a hack to check Details# bench.py
import pyperf
from fractions import Fraction as F
from decimal import Decimal as D
from numbers import Integral
runner = pyperf.Runner()
s = 'Fraction.from_decimal'
f = F.from_decimal
class myint:
numerator = 123
denominator = 1
def __int__(self):
return 123
def __repr__(self):
return "myint"
Integral.register(myint)
for v in [1, myint(), D(1)]:
r = s + '(' + repr(v) + ')'
runner.bench_func(r, f, v)
s = 'Fraction.from_float'
f = F.from_float
for v in [1, myint(), 1.1]:
r = s + '(' + repr(v) + ')'
runner.bench_func(r, f, v)diff --git a/Lib/fractions.py b/Lib/fractions.py
index fa722589fb..d7887af9f8 100644
--- a/Lib/fractions.py
+++ b/Lib/fractions.py
@@ -335,23 +335,23 @@ def from_float(cls, f):
Beware that Fraction.from_float(0.3) != Fraction(3, 10).
"""
- if isinstance(f, numbers.Integral):
+ if not isinstance(f, float):
+ if not isinstance(f, (int, numbers.Integral)):
+ raise TypeError("%s.from_float() only takes floats, not %r (%s)" %
+ (cls.__name__, f, type(f).__name__))
return cls(f)
- elif not isinstance(f, float):
- raise TypeError("%s.from_float() only takes floats, not %r (%s)" %
- (cls.__name__, f, type(f).__name__))
return cls._from_coprime_ints(*f.as_integer_ratio())
@classmethod
def from_decimal(cls, dec):
"""Converts a finite Decimal instance to a rational number, exactly."""
from decimal import Decimal
- if isinstance(dec, numbers.Integral):
- dec = Decimal(int(dec))
- elif not isinstance(dec, Decimal):
- raise TypeError(
- "%s.from_decimal() only takes Decimals, not %r (%s)" %
- (cls.__name__, dec, type(dec).__name__))
+ if not isinstance(dec, Decimal):
+ if not isinstance(dec, (int, numbers.Integral)):
+ raise TypeError(
+ "%s.from_decimal() only takes Decimals, not %r (%s)" %
+ (cls.__name__, dec, type(dec).__name__))
+ dec = int(dec)
return cls._from_coprime_ints(*dec.as_integer_ratio())
@classmethod |
|
This is a second part of improvements, cherry-picked from the issue thread. I think this is less controversial, as speedup affects main use-cases (i.e. Decimal's or float's, respectively). With v2 version we haven't speed regressions for integer case. Though, I'm not sure about using that hack. |
This is slightly slower: |
|
This PR is stale because it has been open for 30 days with no activity. |
Documentation build overview
483 files changed ·
|
eendebakpt
left a comment
There was a problem hiding this comment.
Minor nit, but otherwise approved.
Co-authored-by: Pieter Eendebak <pieter.eendebak@gmail.com>
scoder
left a comment
There was a problem hiding this comment.
This change is meant as an optimisation but it impacts the correctness. The code should be reverted to its previous state and then the two special cases can be prepended as suggested below. They should not replace the original code.
| if isinstance(f, float): | ||
| return cls._from_coprime_ints(*f.as_integer_ratio()) |
There was a problem hiding this comment.
For a subclass of float, f.as_integer_ratio() doesn't necessarily return a normalised integer pair. I think we should restrict this optimisation to exact floats.
| if isinstance(f, float): | |
| return cls._from_coprime_ints(*f.as_integer_ratio()) | |
| if type(f) is float: | |
| return cls._from_coprime_ints(*f.as_integer_ratio()) |
The intention is to optimise the common case, and that is certainly (and by a margin) exact float, not arbitrary subclasses.
We then need to keep the fallback code that handles subclasses, as it was before.
There was a problem hiding this comment.
This affects only float subclasses. The float.as_integer_ration was already documented to return coprime ints.
| if isinstance(dec, Decimal): | ||
| return cls._from_coprime_ints(*dec.as_integer_ratio()) |
There was a problem hiding this comment.
| if isinstance(dec, Decimal): | |
| return cls._from_coprime_ints(*dec.as_integer_ratio()) | |
| if type(dec) is Decimal: | |
| return cls._from_coprime_ints(*dec.as_integer_ratio()) |
|
Hmm, I looked through the code a bit more and found that this assumption is made at other places as well, including the constructor and |
Uh oh!
There was an error while loading. Please reload this page.